package com.tau.egonetwork.gui.core;

import java.util.HashMap;
import java.util.TreeMap;
import java.util.concurrent.Future;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MenuEvent;
import org.eclipse.swt.events.MenuListener;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import com.tau.egonetwork.common.Log;
import com.tau.egonetwork.common.VisualizationPreferencesFile;
import com.tau.egonetwork.facebook.FacebookMainProfile;
import com.tau.egonetwork.gui.Environment;
import com.tau.egonetwork.gui.core.graphs.GroupsVisualizeTab;
import com.tau.egonetwork.gui.core.graphs.NetworkVisualizeTab;
import com.tau.egonetwork.profiles.Group;
import com.tau.egonetwork.profiles.MainProfile;
import com.tau.egonetwork.profiles.Profile;
import com.tau.egonetwork.profiles.ProfileFile;
import com.tau.egonetwork.profiles.ProfileFile.SocialNetworks;
import com.tau.egonetwork.threads.AnalyzeDataThread;
import com.tau.egonetwork.threads.ClusteringThread;
import com.tau.egonetwork.threads.core.EgoNetworkCallable;
import com.tau.egonetwork.threads.core.ThreadPool;

public abstract class ProfileTab<T extends Comparable<T>> extends BasicTab {

	private static final String KEY_PROFILE = "profile";
	private static final String KEY_GROUP = "group";
	private static final String KEY_NETWORK_VISUAL = "networkvisual";
	private static final String KEY_GROUPS_VISUAL = "groupsvisual";
	
	private static final String FRIENDS_TAB = "User and Friends";
	private static final String GROUPS_TAB = "Groups";
	private static final String REPORTS_TAB = "Reports";
	private static final String NETWORK_VISUALIZATION_TAB = "Network Visualization";
	private static final String GROUPS_VISUALIZATION_TAB = "Groups Visualization";
	
	private static final String REPORT_MISMATCH = "Mutual Friends Mismatch Report";
	private static final String REPORT_FRIENDS_IN_GROUPS = "Friends in more than one group?";
	
	protected abstract SocialNetworks getNetwork();
	
	protected MainProfile<T> profile;
	
	/* in order to suport reverting back to before changes,
	 * when tab is closed (and not saved), we'll build a
	 * copy of the original data when tab is first opened
	 * and copy it back to object if needed (when closing
	 * without saving) */
	private MainProfile<T> originalProfile;
	private boolean needsToBuildOriginal = true;
	private TreeMap<T, Profile<T>> originalProfilesTree;
	
	private Shell parentShell;
		
	private Button newButton;
	private Button editButton;
	private Button saveButton;
	private Button deleteButton;
	
	private Button prioritizeButton;
	
	private Button analyzeButton;
	private Button clusterButton;
	
	private Button visualizeNetworkButton;
	private Button visualizeGroupsButton;
	
	private Text searchText;
		
	public ProfileTab(MainProfile<T> p) {
		profile = p;
	}
	
	public void issueSave() {
		if (saveButton.isEnabled()) {
			saveButton.notifyListeners(SWT.Selection, new Event());
		}
	}
	
	public boolean isSaveButtonEnabled() {
		return saveButton.isEnabled();
	}
	
	@SuppressWarnings("unchecked")
	@Override
	protected void buildTab(Composite tabComposite) {
				
        Composite userComposite = new Composite(tabComposite, SWT.NONE);
        
        GridLayout userCompositeLayout = new GridLayout(1, true);
        userCompositeLayout.verticalSpacing = 0;
        userCompositeLayout.horizontalSpacing = 0;
        userCompositeLayout.marginHeight = 0;
        userCompositeLayout.marginWidth = 0;
        userComposite.setLayout(userCompositeLayout);
        userComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        
        Composite toolbarComposite = new Composite(userComposite, SWT.BORDER_DASH);
        GridLayout toolbarCompositeLayout = new GridLayout(12, false);
        toolbarCompositeLayout.marginWidth = 2;
        toolbarCompositeLayout.marginHeight = 2;
        toolbarCompositeLayout.horizontalSpacing = 1;
        toolbarComposite.setLayout(toolbarCompositeLayout);
        GridData toolbarCompositeLayoutData = new GridData(SWT.FILL, SWT.TOP, true, false);
        toolbarComposite.setLayoutData(toolbarCompositeLayoutData);
        toolbarComposite.setBackgroundImage(Environment.BG_TOOLBAR);
        toolbarComposite.setBackgroundMode(SWT.INHERIT_FORCE);
        
        parentShell = tabComposite.getShell();
        
        newButton = new Button(toolbarComposite, SWT.FLAT);
        newButton.setImage(Environment.ICON_NEW);
        newButton.setText("New");
        newButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
        
        editButton = new Button(toolbarComposite, SWT.FLAT);
        editButton.setImage(Environment.ICON_EDIT);
        editButton.setText("Edit");
        editButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
        editButton.setEnabled(false);

        saveButton = new Button(toolbarComposite, SWT.FLAT);
        saveButton.setImage(Environment.ICON_SAVE);
        saveButton.setText("Save");
        saveButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
        saveButton.setEnabled(false);
        Environment.enableSaveMenuItem(false);
        
        deleteButton = new Button(toolbarComposite, SWT.FLAT);
        deleteButton.setImage(Environment.ICON_DELETE);
        deleteButton.setText("Delete");
        deleteButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
        deleteButton.setEnabled(false);
                
        {
        	Label toolBarSeparator = new Label(toolbarComposite, SWT.SEPARATOR);
            GridData toolBarSeparatorGridData = new GridData(SWT.CENTER, SWT.FILL, false, true);
            toolBarSeparatorGridData.heightHint = deleteButton.getSize().y;
            toolBarSeparator.setLayoutData(toolBarSeparatorGridData);
        }
        
        prioritizeButton = new Button(toolbarComposite, SWT.FLAT);
        Image i = Environment.ICON_PRIORITIZE;
        ImageData id = i.getImageData().scaledTo(19, 19);
        prioritizeButton.setImage(new Image(parentShell.getDisplay(), id));
        prioritizeButton.setText("Prioritize Groups");
        prioritizeButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
        prioritizeButton.setEnabled(profile.getGroupsList() != null && profile.getGroupsList().size() > 0);

        {
	        Label toolBarSeparator = new Label(toolbarComposite, SWT.SEPARATOR);
	        GridData toolBarSeparatorGridData = new GridData(SWT.CENTER, SWT.FILL, false, true);
	        toolBarSeparatorGridData.heightHint = deleteButton.getSize().y;
	        toolBarSeparator.setLayoutData(toolBarSeparatorGridData);
        }
        
        analyzeButton = new Button(toolbarComposite, SWT.FLAT);
        analyzeButton.setImage(Environment.ICON_EXCEL);
        analyzeButton.setText("Analyze Data");
        analyzeButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));
        
        clusterButton = new Button(toolbarComposite, SWT.FLAT);
        clusterButton.setImage(Environment.ICON_CLUSTER);
        clusterButton.setText("Calculate Clusters");
        clusterButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));        
        
        {
	        Label toolBarSeparator = new Label(toolbarComposite, SWT.SEPARATOR);
	        GridData toolBarSeparatorGridData = new GridData(SWT.CENTER, SWT.FILL, false, true);
	        toolBarSeparatorGridData.heightHint = deleteButton.getSize().y;
	        toolBarSeparator.setLayoutData(toolBarSeparatorGridData);
        }

        visualizeNetworkButton = new Button(toolbarComposite, SWT.FLAT);
        visualizeNetworkButton.setImage(Environment.ICON_VISUALIZE_1);
        visualizeNetworkButton.setText("View Network Graph");
        visualizeNetworkButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));

        visualizeGroupsButton = new Button(toolbarComposite, SWT.FLAT);
        visualizeGroupsButton.setImage(Environment.ICON_VISUALIZE_2);
        visualizeGroupsButton.setText("View Groups Graph");
        visualizeGroupsButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false));

        final TabFolder detailsTabs = new TabFolder(userComposite, SWT.NONE);
        detailsTabs.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        
        TabItem userDetailsItem = new TabItem(detailsTabs, SWT.NONE);
        userDetailsItem.setText(FRIENDS_TAB);
        
        Composite friendsComposite = new Composite(detailsTabs, SWT.NONE);
        friendsComposite.setLayout(new GridLayout(1, true));
        friendsComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        
        final Tree userTree = new Tree(friendsComposite, SWT.NONE);
        userTree.setBackground(Environment.COLOR_WHITE);
        userTree.setBackgroundMode(SWT.INHERIT_FORCE);
        userTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        
        final TreeItem userRoot = new TreeItem(userTree, SWT.NONE);
        
        originalProfile = (MainProfile<T>)profile.clone();
        originalProfilesTree = new TreeMap<T, Profile<T>>();
        
        createFriendsSubTree(userRoot);
        createTreeMenu(userTree);
        
		Composite separatorComposite = new Composite(friendsComposite, SWT.NONE);
		GridLayout separatorCompositeGridLayout = new GridLayout(1, true);
		separatorCompositeGridLayout.marginWidth = 0;
		separatorCompositeGridLayout.marginLeft = 0;
		separatorCompositeGridLayout.marginRight = 0;
		separatorComposite.setLayout(separatorCompositeGridLayout);
		separatorComposite.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		
		Label separator = new Label(separatorComposite, SWT.SEPARATOR | SWT.HORIZONTAL);
		GridData separatorGridData = new GridData(SWT.FILL, SWT.CENTER, true, false);
		separatorGridData.horizontalSpan = 2;
		separator.setLayoutData(separatorGridData);
        
        Composite searchComposite = new Composite(friendsComposite, SWT.NONE);
        GridLayout searchCompositeGridLayout = new GridLayout(3, false);
        searchCompositeGridLayout.marginWidth = 0;
        searchCompositeGridLayout.marginHeight = 0;
        searchComposite.setLayout(searchCompositeGridLayout);
        searchComposite.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false));
        
        searchText = new Text(searchComposite, SWT.BORDER);
        searchText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true));
        searchText.setTextLimit(50);
        
        final Button searchButton = new Button(searchComposite, SWT.PUSH);
        searchButton.setImage(Environment.ICON_SEARCH);
        searchButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, true));
        searchButton.setEnabled(false);
        
        final Button clearButton = new Button(searchComposite, SWT.PUSH);
        clearButton.setText("clear");
        clearButton.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, true));
        clearButton.setEnabled(false);
        
		searchText.addListener(SWT.KeyUp, new Listener() {

			@Override
			public void handleEvent(Event event) {
				searchButton.setEnabled(!searchText.getText().trim().equals(""));
			}
			
		});
		
		searchButton.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				clearButton.setEnabled(true);
				userRoot.removeAll();
				createFriendsSubTree(userRoot);
			}
		});
		
		clearButton.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				searchText.setText("");
				searchButton.setEnabled(false);
				clearButton.setEnabled(false);
				userRoot.removeAll();
				createFriendsSubTree(userRoot);
			}
		});
        
        userDetailsItem.setControl(friendsComposite);
        
        userTree.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				editButton.setEnabled(userTree.getSelectionCount() > 0);
				deleteButton.setEnabled(userTree.getSelectionCount() > 0
						&& userTree.getSelection()[0].getParentItem() != null);
			}
		});
        
        final Tree groupsTree = new Tree(detailsTabs, SWT.NONE);
    	TabItem groupsItem = new TabItem(detailsTabs, SWT.NONE);
    	groupsItem.setText(GROUPS_TAB);
         
        groupsTree.setBackground(Environment.COLOR_WHITE);
        groupsTree.setBackgroundMode(SWT.INHERIT_FORCE);
        groupsTree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        groupsItem.setControl(groupsTree);

        if (profile.getGroupsList() != null) {
      
            createGroupsTree(groupsTree);
            createTreeMenu(groupsTree);
            
            groupsTree.addListener(SWT.Selection, new Listener() {
    			
    			@Override
    			public void handleEvent(Event event) {
    				editButton.setEnabled(groupsTree.getSelectionCount() > 0);
    				deleteButton.setEnabled(groupsTree.getSelectionCount() > 0
    						 && groupsTree.getSelection()[0].getParentItem() != null);
    			}
    		});

            
            needsToBuildOriginal = false;
            originalProfilesTree.clear();
        }
        
        detailsTabs.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				userTree.notifyListeners(SWT.Selection, new Event());
				if (groupsTree != null)
					groupsTree.notifyListeners(SWT.Selection, new Event());
			}
		});
        
        TabItem reportsItem = new TabItem(detailsTabs, SWT.NONE);
        reportsItem.setText(REPORTS_TAB);
        
        reportsItem.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				editButton.setEnabled(false);
				deleteButton.setEnabled(false);
			}
		});
        
        Composite reportsComposite = new Composite(detailsTabs, SWT.NONE);
        reportsComposite.setLayout(new GridLayout(1, false));
        reportsComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        
        reportsComposite.setBackground(Environment.COLOR_WHITE);
        reportsComposite.setBackgroundMode(SWT.INHERIT_FORCE);
        
        Composite selectReportComposite = new Composite(reportsComposite, SWT.NONE);
        selectReportComposite.setLayout(new GridLayout(2, false));
        selectReportComposite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
        
        final Combo selectReportCombo = new Combo(selectReportComposite, SWT.BORDER);
        selectReportCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
        
        selectReportCombo.add(REPORT_MISMATCH);
        selectReportCombo.add(REPORT_FRIENDS_IN_GROUPS);
                
        Button selectReportButton = new Button(selectReportComposite, SWT.PUSH);
        selectReportButton.setText("GO");
        selectReportButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false));
        
        final Table reportResultTable = new Table(reportsComposite, SWT.SINGLE | SWT.FULL_SELECTION | SWT.V_SCROLL | SWT.H_SCROLL);
        reportResultTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        reportResultTable.setBackground(Environment.COLOR_TABLE_BG);
        reportResultTable.setBackgroundMode(SWT.INHERIT_FORCE);
     
        selectReportButton.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				if (selectReportCombo.getSelectionIndex() >= 0) {
					if (selectReportCombo.getText().equals(REPORT_MISMATCH)) {
						generateMismatchReport(reportResultTable);
					} else if (selectReportCombo.getText().equals(REPORT_FRIENDS_IN_GROUPS)) {
						generateFriendsInGroupsReport(reportResultTable);
					}
				}
			}
		});
        
        reportResultTable.addListener(SWT.Paint, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				if (reportResultTable.getColumnCount() > 0) {
					for (TableColumn column : reportResultTable.getColumns()) {
						column.setWidth(reportResultTable.getSize().x / reportResultTable.getColumns().length);
					}
				}
			}
		});

        reportsItem.setControl(reportsComposite);

        newButton.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				
				Log.writeLineToLog("ProbileTab (" + getTabText() + ") newButtonClicked on selectedTab "
									+ (detailsTabs.getSelection().length == 0
										? "null"
										: detailsTabs.getSelection()[0].getText()));
        
				TabItem[] items = detailsTabs.getSelection();
				if (items.length == 1) {
					TabItem selectedTab = items[0];
					if (selectedTab.getText().equals(FRIENDS_TAB)) {
						if (openNewProfileDialog(parentShell)) {
							//changes were made, need to redraw tree:
							userRoot.removeAll();
							createFriendsSubTree(userRoot);
						}
					} else {
						if (openNewGroupDialog(parentShell)) {
							//changed were made, need to redraw tree:
							groupsTree.removeAll();
							createGroupsTree(groupsTree);
						}
					}
				}
			}
		});
        
        editButton.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {

				Log.writeLineToLog("ProbileTab (" + getTabText() + ") editButtonClicked on selectedTab "
						+ (detailsTabs.getSelection().length == 0
							? "null"
							: detailsTabs.getSelection()[0].getText()));

				TabItem[] items = detailsTabs.getSelection();
				if (items.length == 1) {
					TabItem selectedTab = items[0];
					if (selectedTab.getText().equals(FRIENDS_TAB)) {
						if (userTree.getSelectionCount() == 1) {
							if (openEditProfileDialog(parentShell,
									(Profile<T>)userTree.getSelection()[0].getData(KEY_PROFILE))) {
								//changes were made, need to redraw tree:
								userRoot.removeAll();
								createFriendsSubTree(userRoot);
							}
						}
					} else {
						if (groupsTree.getSelectionCount() == 1) {
							Group<T> selectedGroup = (Group<T>)groupsTree.getSelection()[0].getData(KEY_GROUP);
							if (selectedGroup != null) {
								if (openEditGroupDialog(parentShell, selectedGroup)) {
									//changed were made, need to redraw tree:
									groupsTree.removeAll();
									createGroupsTree(groupsTree);
								}
							} else {
								if (openEditProfileDialog(parentShell,
										(Profile<T>)groupsTree.getSelection()[0].getData(KEY_PROFILE))) {
									//changed were made, need to redraw tree:
									userRoot.removeAll();
									createFriendsSubTree(userRoot);
								}								
							}
						}
					}
				}
			}
		});
        
        saveButton.addListener(SWT.Selection, new Listener() {

			@Override
			public void handleEvent(Event event) {

				Log.writeLineToLog("ProbileTab (" + getTabText() + ") saveButtonClicked on selectedTab "
						+ (detailsTabs.getSelection().length == 0
							? "null"
							: detailsTabs.getSelection()[0].getText()));

				
				ThreadPool.submitTask(new EgoNetworkCallable<Future<Void>, Void>() {
					
					@Override
					public Void call() throws Exception {
						ProfileFile.saveToFile(getNetwork(), profile);
						parentShell.getDisplay().asyncExec(new Runnable() {
							
							@Override
							public void run() {
								if (!saveButton.isDisposed()) {
									saveButton.setEnabled(false);
									Environment.unsavedProfiles--;
							        Environment.enableSaveMenuItem(false);
									
							        //clear search text if exists (so we'll make an original copy of everything!):
							        searchText.setText("");
							        searchButton.setEnabled(false);
							        clearButton.setEnabled(false);
									
									originalProfile = (MainProfile<T>)profile.clone();
									needsToBuildOriginal = true;
									userRoot.removeAll();
									groupsTree.removeAll();
									createFriendsSubTree(userRoot);
									createGroupsTree(groupsTree);
									needsToBuildOriginal = false;
									originalProfilesTree.clear();
								}
							}
						});
						return null;
					}
				});
				
			}
        	
        });
                
        deleteButton.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event event) {
				
				Log.writeLineToLog("ProbileTab (" + getTabText() + ") deleteButtonClicked on selectedTab "
						+ (detailsTabs.getSelection().length == 0
							? "null"
							: detailsTabs.getSelection()[0].getText()));

				TabItem[] items = detailsTabs.getSelection();
				if (items.length == 1) {
					TabItem selectedTab = items[0];
					if (selectedTab.getText().equals(FRIENDS_TAB)) {
						if (userTree.getSelectionCount() == 1) {
							TreeItem selectedItem = userTree.getSelection()[0];
							//check that we're not deleting root:
							if (selectedItem.getParentItem() != null) {
								boolean deleted = false;
								if (selectedItem.getParentItem().equals(userRoot)) {
									//deleting friend:
									EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(parentShell, SWT.ICON_QUESTION,  SWT.YES | SWT.NO);
									mbox.setText("Delete Friend Profile");
									mbox.setMessage("Are you sure you want to delete " + selectedItem.getText() + "?");
									if (mbox.open() == SWT.YES) {
										deleteFriend((Profile<T>)selectedItem.getData(KEY_PROFILE));
										deleted = true;
									}
								} else {
									//deleting friendship:
									EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(parentShell, SWT.ICON_QUESTION, SWT.YES | SWT.NO);
									mbox.setText("Delete Frienship");
									mbox.setMessage("Are you sure you want to delete frienship between "
											+ selectedItem.getText() + " and "
											+ selectedItem.getParentItem().getText() + "?");
									if (mbox.open() == SWT.YES) {
										deleteFriendship((Profile<T>)selectedItem.getData(KEY_PROFILE),
												(Profile<T>)selectedItem.getParentItem().getData(KEY_PROFILE));
										deleted = true;
									}
								}
								if (deleted) {
									//redraw tree:
									if (!saveButton.getEnabled()) {
										//profile was saved before, not now:
										Environment.unsavedProfiles++;
									}
									saveButton.setEnabled(true);
							        Environment.enableSaveMenuItem(true);
									userRoot.removeAll();
									createFriendsSubTree(userRoot);
									groupsTree.removeAll();
									createGroupsTree(groupsTree);
								}
							}
						}
					} else { //this is the groups tab:
						if (groupsTree.getSelectionCount() == 1) {
							TreeItem selectedItem = groupsTree.getSelection()[0];
							boolean deleted = false;
							if (selectedItem.getParentItem() == null) {
								//deleting group:
								EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(parentShell, SWT.ICON_QUESTION,  SWT.YES | SWT.NO);
								mbox.setText("Delete Group");
								mbox.setMessage("Are you sure you want to delete '" + selectedItem.getText() + "' group?");
								if (mbox.open() == SWT.YES) {
									deleteGroup((Group<T>)selectedItem.getData(KEY_GROUP));
									deleted = true;
								}
							} else {
								//deleting group membership:
								EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(parentShell, SWT.ICON_QUESTION, SWT.YES | SWT.NO);
								mbox.setText("Delete Group Membership");
								mbox.setMessage("Are you sure you want to remove "
										+ selectedItem.getText() + " from '"
										+ selectedItem.getParentItem().getText() + "' group?");
								if (mbox.open() == SWT.YES) {
									deleteGroupMembership((Group<T>)selectedItem.getParentItem().getData(KEY_GROUP),
											(Profile<T>)selectedItem.getData(KEY_PROFILE));
									deleted = true;
								}
							}
							if (deleted) {
								//redraw tree:
								if (!saveButton.getEnabled()) {
									//profile was saved before, not now:
									Environment.unsavedProfiles++;
								}
								saveButton.setEnabled(true);
								Environment.enableSaveMenuItem(true);
								userRoot.removeAll();
								createFriendsSubTree(userRoot);
								if (profile.getGroupsList() != null) {
									groupsTree.removeAll();
									createGroupsTree(groupsTree);
								}
							}							
						}						
					}
				}
			}
		});
        
        prioritizeButton.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event arg0) {
				PrioritizeGroupsDialog<T> pgd = new PrioritizeGroupsDialog<T>(parentShell, profile);
				boolean changed = (Boolean)pgd.open();
				if (changed) {
					if (!saveButton.getEnabled()) {
						Environment.unsavedProfiles++;
					}
					saveButton.setEnabled(true);
					Environment.enableSaveMenuItem(true);					
				}
			}
		});
        
        analyzeButton.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event arg0) {
				
				//prompt user for location to create file in:
				FileDialog fileDialog = new FileDialog(parentShell, SWT.SAVE);
				fileDialog.setOverwrite(true); //prompt user if overwriting existing file
				fileDialog.setFileName(profile.getName());
				fileDialog.setFilterNames(new String[] { "Excel Workbook (*.xlsx)", "Microsoft 97-2003 Excel Workbook (*.xls)" });
				fileDialog.setFilterExtensions(new String[] { "*.xlsx", "*.xls" });
				final String filename = fileDialog.open();

				if (filename != null) {
					AnalyzeDataThread.invoke(parentShell, profile, filename);
					saveButton.setEnabled(true);
					Environment.unsavedProfiles++;
					Environment.enableSaveMenuItem(true);
				}
				
			}
		});
        
        clusterButton.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event arg0) {
				NumberInputDialog nid = new NumberInputDialog(parentShell);
				nid.setText("Cluster Calculator");
				nid.setMessageText("Please enter a number to cluster by: ");
				nid.setLimits(1, profile.getFriendsList() == null ? 1 : profile.getFriendsList().size());
				int clusterBy = nid.open();
				if (clusterBy > 0) {
					
					//prompt user for location to create file in:
					FileDialog fileDialog = new FileDialog(parentShell, SWT.SAVE);
					fileDialog.setOverwrite(true); //prompt user if overwriting existing file
					fileDialog.setFileName(profile.getName() + " " + clusterBy + " Clusters");
					fileDialog.setFilterNames(new String[] { "Excel Workbook (*.xlsx)", "Microsoft 97-2003 Excel Workbook (*.xls)" });
					fileDialog.setFilterExtensions(new String[] { "*.xlsx", "*.xls" });
					final String filename = fileDialog.open();

					if (filename != null)
						ClusteringThread.invoke(parentShell, profile, clusterBy, filename);
				}
			}
		});
        
        visualizeNetworkButton.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event arg0) {

				//first, check visualization preferences were chosen:
				if (!VisualizationPreferencesFile.isVisualizationPreferencesFileExists()) {
					EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(parentShell, SWT.ICON_ERROR, SWT.OK);
					mbox.setText("Visualize Groups");
					mbox.setMessage("Visualization Preferences have yet to be set. Go to Help > Visualization Preferences to set them and then try again.");
					mbox.open();
					return;
				}

				//second, alert if no analyzed data yet:
				if (!profile.getAnalyzedDataExists()) {
					EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(parentShell, SWT.ICON_QUESTION, SWT.YES | SWT.NO);
					mbox.setText("Visualize Groups");
					mbox.setMessage("Please note there is no up-to-date analyzed data available! Do you wish to conitnue?");
					if (mbox.open() == SWT.NO)
						return;
				}

				TabItem visualizeTab = null;
				//check if tab of visualization is already open:
				TabItem[] tabs = detailsTabs.getItems();
				for (TabItem ti : tabs) {
					if (ti.getData(KEY_NETWORK_VISUAL) != null) {
						visualizeTab = ti;
						break;
					}
				}
				
				if (visualizeTab == null) {
					//create new tab for visualization
					visualizeTab = new TabItem(detailsTabs, SWT.NONE);
					visualizeTab.setText(NETWORK_VISUALIZATION_TAB);
					visualizeTab.setData(KEY_NETWORK_VISUAL, KEY_NETWORK_VISUAL);
					new NetworkVisualizeTab<T>(visualizeTab, parentShell.getDisplay())
						.buildTab(detailsTabs, profile);
				}
				
				//bring tab to focus:
				detailsTabs.setSelection(visualizeTab);
			}
        });
        
        visualizeGroupsButton.addListener(SWT.Selection, new Listener() {
			
			@Override
			public void handleEvent(Event arg0) {
				
				//first, check visualization preferences were chosen:
				if (!VisualizationPreferencesFile.isVisualizationPreferencesFileExists()) {
					EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(parentShell, SWT.ICON_ERROR, SWT.OK);
					mbox.setText("Visualize Groups");
					mbox.setMessage("Visualization Preferences have yet to be set. Go to Help > Visualization Preferences to set them and then try again.");
					mbox.open();
					return;
				}
				
				//second, check there are four priorities. If not, make sure user is aware of that:
				if (VisualizationPreferencesFile.getAlertMeWhenUnder4()) {
					if (profile.getGroupsList() == null || profile.getGroupsList().getPriority(4) == null) {
						EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(parentShell, SWT.ICON_WARNING, SWT.YES | SWT.NO);
						mbox.setText("Visualize Groups");
						mbox.setMessage("Not all 4 group priorities have been selected. Are you sure you want to process?");
						if (mbox.open() == SWT.NO)
							return;
					}
				}
				
				//third, check there are no overlaps:
				HashMap<Profile<T>, Integer> groupCount = new HashMap<Profile<T>, Integer>();
				countFriendsInGroups(groupCount, null);
				boolean violates = false;
				for (Profile<T> p : groupCount.keySet()) {
					if (groupCount.get(p) > 1) {
						violates = true;
						break;
					}
				}
				if (violates) {
					EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(parentShell, SWT.ICON_ERROR, SWT.OK);
					mbox.setText("Visualize Groups");
					mbox.setMessage("Some profiles appear in more than groups. It is recommended to run the \"Friends in more than one group?\" report. Graph results would be unpredictable in this case, and as such cannot be displayed. Please run the report and fix the problem, then try again.");
					mbox.open();
					return;
				}
				
				//fourth, alert if no analyzed data yet:
				if (!profile.getAnalyzedDataExists()) {
					EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(parentShell, SWT.ICON_QUESTION, SWT.YES | SWT.NO);
					mbox.setText("Visualize Groups");
					mbox.setMessage("Please note there is no up-to-date analyzed data available! Do you wish to conitnue?");
					if (mbox.open() == SWT.NO)
						return;
				}
				
				//we've got here, so let's show the graph:
				
				TabItem visualizeTab = null;
				//check if tab of visualization is already open:
				TabItem[] tabs = detailsTabs.getItems();
				for (TabItem ti : tabs) {
					if (ti.getData(KEY_GROUPS_VISUAL) != null) {
						visualizeTab = ti;
						break;
					}
				}
				
				if (visualizeTab == null) {
					//create new tab for visualization
					visualizeTab = new TabItem(detailsTabs, SWT.NONE);
					visualizeTab.setText(GROUPS_VISUALIZATION_TAB);
					visualizeTab.setData(KEY_GROUPS_VISUAL, KEY_GROUPS_VISUAL);
					new GroupsVisualizeTab<T>(visualizeTab, parentShell.getDisplay())
						.buildTab(detailsTabs, profile);
				}
				
				//bring tab to focus:
				detailsTabs.setSelection(visualizeTab);				
			}
		});
  
	}
	
	@SuppressWarnings("unchecked")
	private void createFriendsSubTree(TreeItem root) {
		
        root.setText(profile.getName());
        root.setData(KEY_PROFILE, profile);
		
        if (profile.getFriendsList() != null) {
        	
        	if (needsToBuildOriginal)
        		originalProfile.createFriendsList();
	        
        	for (Profile<T> p : profile.getFriendsList()) {
        		
        		if (searchText != null &&
        				!p.getName().toLowerCase().startsWith(searchText.getText().toLowerCase())) {
        			continue;
        		}

        		TreeItem friendItem = new TreeItem(root, SWT.NONE);
	        	friendItem.setText(p.getName());
	        	friendItem.setData(KEY_PROFILE, p);
	        	
	        	if (needsToBuildOriginal) {
	        		Profile<T> clonedProfile = (Profile<T>)p.clone();
	        		originalProfile.getFriendsList().add(clonedProfile);
	        		originalProfilesTree.put(clonedProfile.getID(), clonedProfile);
	        	}
	        	
	        }
        	
        	TreeItem[] items = root.getItems();
        	
        	for (int i = 0; i < items.length; i++) {
        		
        		Profile<T> p = (Profile<T>)items[i].getData(KEY_PROFILE);
        		
	        	if (p.getFriendsList() != null) {
	        		
	        		if (needsToBuildOriginal) {
	        			originalProfilesTree.get(p.getID()).createFriendsList();
	        		}
	        		
		        	for (Profile<T> m : p.getFriendsList()) {

		        		TreeItem mutualFriendItem = new TreeItem(items[i], SWT.NONE);
		        		mutualFriendItem.setText(m.getName());
		        		mutualFriendItem.setData(KEY_PROFILE, m);
		        		
		        		if (needsToBuildOriginal) {
		        			originalProfilesTree.get(p.getID()).getFriendsList().add(originalProfilesTree.get(m.getID()));
		        		}
		        		
		        	}
	        	}
        	}
        }
                
        root.setExpanded(true);
        editButton.setEnabled(false);
        deleteButton.setEnabled(false);
        
	}
	
	@SuppressWarnings("unchecked")
	private void createGroupsTree(Tree groupsTree) {
		
    	if (needsToBuildOriginal) {
    		originalProfile.createGroupsList();
    	}
    	    	
    	for (Group<T> g : profile.getGroupsList()) {
        	    		
        	TreeItem groupItem = new TreeItem(groupsTree, SWT.NONE);
        	groupItem.setText(g.getGroupName());
        	groupItem.setData(KEY_GROUP, g);
        	
        	Group<T> clonedGroup = null;
        	
        	if (needsToBuildOriginal) {
        		clonedGroup = (Group<T>)g.clone();
        		originalProfile.getGroupsList().add(clonedGroup);
        	}
        	
        	for (Profile<T> p : g) {
        		
        		TreeItem friendInGroupItem = new TreeItem(groupItem, SWT.NONE);
        		friendInGroupItem.setText(p.getName());
        		friendInGroupItem.setData(KEY_PROFILE, p);
        		
        		if (clonedGroup != null)
        			clonedGroup.add(originalProfilesTree.get(p.getID()));
        	}
        }
    	
    	if (needsToBuildOriginal) {
    		for (int i = 1; i <= 4; i++) {
    			Group<T> p = profile.getGroupsList().getPriority(i);
    			if (p != null) {
	    			int indx = originalProfile.getGroupsList().indexOf(p);
	    			if (indx > 0) {
	    				originalProfile.getGroupsList().setPriority(
	    						i, originalProfile.getGroupsList().get(indx));
	    			}
	    		}
    		}
    	}

        editButton.setEnabled(false);
        deleteButton.setEnabled(false);
	}
	
	private void createTreeMenu(final Tree tree) {
		
		final Menu menu = new Menu(tree);
		tree.setMenu(menu);
		
		menu.addMenuListener(new MenuListener() {
			
			@SuppressWarnings("unchecked")
			@Override
			public void menuShown(MenuEvent e) {
				{
					MenuItem[] items = menu.getItems();
					for (MenuItem item : items) {
						item.dispose();
					}
				}
				
				{
					TreeItem[] items = tree.getSelection();
					if (items.length == 1) {
						
						MenuItem editMenuItem = new MenuItem(menu, SWT.NONE);
						editMenuItem.setText("Edit");
						editMenuItem.addListener(SWT.Selection, new Listener() {
							
							@Override
							public void handleEvent(Event event) {
								editButton.notifyListeners(SWT.Selection, new Event());
							}
						});
						
						Profile<T> itemProfile = (Profile<T>)items[0].getData(KEY_PROFILE);
						if (itemProfile == null || !(itemProfile instanceof FacebookMainProfile)) {
							MenuItem deleteMenuItem = new MenuItem(menu, SWT.NONE);
							deleteMenuItem.setText("Delete");
							deleteMenuItem.addListener(SWT.Selection, new Listener() {

								@Override
								public void handleEvent(Event event) {
									deleteButton.notifyListeners(SWT.Selection, new Event());
								}
								
							});
						}

					}
				}
			}
			
			@Override
			public void menuHidden(MenuEvent e) {
			}
		});
	}
	
	private boolean openNewProfileDialog(Shell shell) {
		
		ProfileEditDialog<T> ped = getNewProfileDialog(shell);
		ped.setText("New Profile");
		boolean createdNew = (Boolean)ped.open();
		if (createdNew) {
			Log.writeLineToLog("openNewProfileDialog::createdNew = true");
			if (!saveButton.getEnabled()) {
				//profile was saved before, not now:
				Environment.unsavedProfiles++;
			}
			saveButton.setEnabled(true);
			Environment.enableSaveMenuItem(true);
		}
		return createdNew;
	}
	
	protected abstract ProfileEditDialog<T> getNewProfileDialog(Shell shell);
	
	private boolean openEditProfileDialog(Shell shell, Profile<T> profileToEdit) {
		
		ProfileEditDialog<T> ped = new ProfileEditDialog<T>(shell, profileToEdit, profile);
		ped.setText("Edit " + profileToEdit.getName());
		boolean changed = (Boolean)ped.open();
		if (changed) {
			Log.writeLineToLog("openEditProfileDialog::changed = true");
			if (!saveButton.getEnabled()) {
				//profile was saved before, not now:
				Environment.unsavedProfiles++;
			}
			saveButton.setEnabled(true);
			Environment.enableSaveMenuItem(true);
		}
		return changed;
	}
	
	private boolean openNewGroupDialog(Shell shell) {
		
		GroupEditDialog<T> ged = new GroupEditDialog<T>(shell, profile);
		ged.setText("New Group");
		boolean createdNew = (Boolean)ged.open();
		if (createdNew) {
			Log.writeLineToLog("openNewGroupDialog::createdNew = true");
			if (!saveButton.getEnabled()) {
				//profile was saved before, not now:
				Environment.unsavedProfiles++;
			}
			saveButton.setEnabled(true);
			Environment.enableSaveMenuItem(true);
		}
		return createdNew;
	}
	
	private boolean openEditGroupDialog(Shell shell, Group<T> groupToEdit) {
		
		GroupEditDialog<T> ged = new GroupEditDialog<T>(shell, groupToEdit, profile);
		ged.setText("Edit " + groupToEdit.getGroupName());
		boolean changed = (Boolean)ged.open();
		if (changed) {
			Log.writeLineToLog("openEditGroupDialog::changed = true");
			if (!saveButton.getEnabled()) {
				//profile was saved before, not now:
				Environment.unsavedProfiles++;
			}
			saveButton.setEnabled(true);
			Environment.enableSaveMenuItem(true);
		}
		return changed;
		
	}
	
	private void deleteFriend(Profile<T> profileToDelete) {
		
		//delete from friends:
		
		if (profile.getFriendsList() != null) {
			for (Profile<T> p : profile.getFriendsList()) {
				if (p.getFriendsList() != null) {
					p.getFriendsList().remove(profileToDelete);
				}
			}
			profile.getFriendsList().remove(profileToDelete);
		}
		
		//delete from groups:
		
		if (profile.getGroupsList() != null) {
			for (Group<T> g : profile.getGroupsList()) {
				g.remove(profileToDelete);
			}
		}		
	}
	
	private void deleteFriendship(Profile<T> friend1, Profile<T> friend2) {
		
		if (friend1.getFriendsList() != null && friend2.getFriendsList() != null) {
			friend1.getFriendsList().remove(friend2);
			friend2.getFriendsList().remove(friend1);
		}
	}
	
	private void deleteGroup(Group<T> groupToDelete) {
		if (profile.getGroupsList() != null) {
			profile.getGroupsList().remove(groupToDelete);
		}
	}
	
	private void deleteGroupMembership(Group<T> group, Profile<T> profile) {
		group.remove(profile);
	}
	
	private void prepareTableReport(Table resultsTable, String[] columns) {
		
		//clear table of all items, columns:
		while (resultsTable.getItemCount() > 0) {
			resultsTable.getItems()[0].dispose();
		}
		while (resultsTable.getColumnCount() > 0) {
			resultsTable.getColumns()[0].dispose();
		}
		
		//make sure these are enabled:
		resultsTable.setLinesVisible(true);
		resultsTable.setHeaderVisible(true);
		
		//create columns for this report:
		for (String s : columns) {
			TableColumn column = new TableColumn(resultsTable, SWT.NONE);
			column.setText(s);
			column.setResizable(false);
			column.setAlignment(SWT.LEFT);
		}
	}
	
	private void generateMismatchReport(Table resultsTable) {
		
		resultsTable.setRedraw(false);

		prepareTableReport(resultsTable,
				new String[] { "Friend #1", "Friend #2" });
				
		//generate report information:
		if (profile.getFriendsList() != null) {
			for (Profile<T> p : profile.getFriendsList()) {
				if (p.getFriendsList() != null) {
					//go over all mutual friends and check p exists in their list!:
					for (Profile<T> m : p.getFriendsList()) {
						
						boolean mismatchFound = false;
						if (m.getFriendsList() == null) {
							mismatchFound = true;
						} else {
							if (!m.getFriendsList().contains(p)) {
								mismatchFound = true;
							}
						}
						
						if (mismatchFound) {
							TableItem item = new TableItem(resultsTable, SWT.NONE);
							item.setBackground(Environment.COLOR_TABLE_ROW);
							item.setText(0, m.getName());
							item.setText(1, p.getName());
						}
						
					}
				}
			}
		}
		
		for (TableColumn column : resultsTable.getColumns()) {
			column.pack();
		}
		
		resultsTable.setRedraw(true);

	}
	
	private void generateFriendsInGroupsReport(Table resultsTable) {
		
		resultsTable.setRedraw(false);
		
		prepareTableReport(resultsTable,
				new String[] { "Friend", "# of Groups", "Groups" } );
		
		HashMap<Profile<T>, Integer> groupCount = new HashMap<Profile<T>, Integer>();
		HashMap<T, String> groupDetails = new HashMap<T, String>();
		
		//generate report information:
		countFriendsInGroups(groupCount, groupDetails);
		
		for (Profile<T> p : groupCount.keySet()) {
			if (groupCount.get(p) > 1) {
				TableItem item = new TableItem(resultsTable, SWT.NONE);
				item.setBackground(Environment.COLOR_TABLE_ROW);
				item.setText(0, p.getName());
				item.setText(1, groupCount.get(p) + "");
				item.setText(2, groupDetails.get(p.getID()));
			}
		}
		
		for (TableColumn column : resultsTable.getColumns()) {
			column.pack();
		}
		
		resultsTable.setRedraw(true);
	}
	
	private void countFriendsInGroups(HashMap<Profile<T>, Integer> groupCount, HashMap<T, String> groupDetails) {
		
		if (profile.getFriendsList() != null && profile.getGroupsList() != null) {
			for (Group<T> g : profile.getGroupsList()) {
				for(Profile<T> p : g) {
					if (groupCount.containsKey(p)) {
						groupCount.put(p, groupCount.get(p) + 1);
						if (groupDetails != null) {
							groupDetails.put(p.getID(),
									groupDetails.get(p.getID()) + ", " + g.getGroupName());
						}
					} else {
						groupCount.put(p, 1);
						if (groupDetails != null)
							groupDetails.put(p.getID(), g.getGroupName());
					}
				}
			}
		}
	}
	
	@Override
	protected String getTabText() {
		return profile.getName();
	}
	
	@Override
	protected boolean onTabClosed() {
		
		Log.writeLineToLog("ProfileTab (" + getTabText() + "): onTabClosed()");
		
		if (saveButton.getEnabled()) {
			
			EgoNetworkMessageBox mbox = new EgoNetworkMessageBox(parentShell, SWT.ICON_QUESTION, SWT.YES | SWT. NO | SWT.CANCEL);
			mbox.setText("Closing Without Saving");
			mbox.setMessage("Do you want to save changes made to " + profile.getName() + "?");
			int result = mbox.open();
			if (result == SWT.CANCEL)
				return false;
			
			if (result == SWT.YES) {
				saveButton.notifyListeners(SWT.Selection, new Event());
			} else {
				//SWT.NO
				profile.copyFrom(originalProfile);
				//we marked this profile as unsaved, but now we're
				//closing the tab, so we have to deduct it:
				Environment.unsavedProfiles--;
			}
		}
		
		Environment.enableSaveMenuItem(false);
		
		return true;
	}
}
